// eee.Sheffield.PZ.Imaging
//
// Copyright ? Ping Zou, 2007
// sg71.cherub@gmail.com


using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using eee.Sheffield.PZ.Math;
using System.IO;

namespace eee.Sheffield.PZ.Imaging.ImageFilter
{
    /// <summary>
    /// Peter thinning algorithm, (Peter, 2005)
    /// Peter I Rockett, An Improved Rotation-Invariant Thinning Algorithm, 
    /// IEEE Transactions on Pattern Analysis and Machine Intelligence, V. 27, No. 10, pp 1671 - 1674, Oct 2005
    /// </summary>
    public class PeterThinning : ImageFilter//, IGraylevelImageFilter
    {
        enum IsSeal {NotSeal, SealAtConnectPoint, SealAtBranchPoint };
             
        #region Fields
        private List<PZPoint> _connectPointList;    // connect pointlist
        private bool[,] _binaryImg;		// thinning result, a binary image (bool matrix)
        private List<LineSegment> _lineSegmentList; //line segments list    
        //private Bitmap _thinningResultImage;  // thinning image
        //private string _flag = "[Image -> Morphology -> Peter Thinning]";        
        #endregion

        #region Properties
        public List<PZPoint> ConnectPointList { get { return _connectPointList; } }
        public bool[,] BinaryImg { get { return _binaryImg; } }
        public List<LineSegment> LineSegmentList { get { return _lineSegmentList; } }
        //public Bitmap ThinningResultImage { get { return _thinningResultImage; } }
        //public string Log() { return _log; }
        //public string Flag() { return _flag; }
        #endregion

        #region Constructor
        public PeterThinning()
        {
            _flag = "[Image -> Morphology -> Peter Thinning]";    
            _binaryImg = null;
            _connectPointList = new List<PZPoint>(0);
            _lineSegmentList = new List<LineSegment>(0);
        }
        #endregion

        #region Apply filter
        //public Bitmap ApplyGraylevelImageFilter(Bitmap srcImage) { return Apply(srcImage, out _log); }
        //public PZMath_matrix ApplyDoubleGraylevelImageFilter(PZMath_matrix srcMatrix) { return ApplyDouble(srcMatrix, out _log); }

        //private Bitmap Apply(Bitmap srcImage, out string logText)
        //{
        //    PZMath_matrix srcMatrix = _converter.ConvertGraylevelBitmapToMatrix(srcImage);
        //    PZMath_matrix dstMatrix = ApplyDouble(srcMatrix, out logText);
        //    Bitmap dstImage = _converter.ConvertMatrixToGraylevelBitmap(dstMatrix);
        //    return dstImage;
        //}     

        protected override PZMath_matrix ApplyDoubleGraylevelImageFilter(PZMath_matrix srcMatrix, out string logText)
        {            
            #region source and dst matrix
            // get source
            int width = srcMatrix.ColumnCount;
            int height = srcMatrix.RowCount;

            // create dstMatrix
            PZMath_matrix dstMatrix = new PZMath_matrix(height, width);
            int heightM2 = height - 2;
            int widthM2 = width - 2;
            #endregion

            // convert source image to binary image
            SISThreshold thres_filter = new SISThreshold();
            int threshold = thres_filter.DoThreshold(srcMatrix);
            string thresholdLog = thres_filter.LogText;
            _binaryImg = thres_filter.DoBinarize(srcMatrix);
            string binarizeLog = thres_filter.LogText;
            

            // thinning
            #region main algorithm, w.r.t. mBinaryImg
            uint uNoIterations = 0;
            bool bPixelDeleted = true;	// Set 'true' if any pixel has been deleted during the current iteration
            Point PixelToDelete = new Point();
            System.Collections.Stack Stack = new System.Collections.Stack();
            const uint uMaxIterations = 100;
            bool x0, x1, x2, x3, x4, x5, x6, x7, x8;
            bool p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12;

            while (uNoIterations < uMaxIterations && bPixelDeleted)
            {
                bPixelDeleted = false;

                // for each line
                for (int y = 2; y < heightM2; y++)
                {
                    // for each pixel
                    for (int x = 2; x < widthM2; x++)
                    {
                        x0 = _binaryImg[x, y];

                        if (!x0)
                            goto End;
                        x1 = _binaryImg[x + 1, y];
                        x2 = _binaryImg[x + 1, y - 1];
                        x3 = _binaryImg[x, y - 1];
                        x4 = _binaryImg[x - 1, y - 1];
                        x5 = _binaryImg[x - 1, y];
                        x6 = _binaryImg[x - 1, y + 1];
                        x7 = _binaryImg[x, y + 1];
                        x8 = _binaryImg[x + 1, y + 1];

                        // Skip horizontal 2-pixel wide line
                        p10 = _binaryImg[x, y + 2];
                        if (!x3 && x7 && !p10)
                            goto End;

                        // Skip vertical 2-pixel wide line
                        p1 = _binaryImg[x + 2, y];
                        if (!x5 && x1 && !p1)
                            goto End;


                        // Check if pixel is the extremity of a diagonal line
                        p9 = _binaryImg[x - 1, y + 2];
                        p10 = _binaryImg[x, y + 2];
                        p11 = _binaryImg[x + 1, y + 2];
                        if (!x1 && !x2 && !x3 && !x4 && !x5 && x6 && x7 && !x8 && p9 && !p10 && !p11)
                            goto End;

                        if (!x1 && !x2 && !x3 && !x4 && !x5 && !x6 && x7 && x8 && !p9 && !p10 && p11)
                            goto End;

                        p3 = _binaryImg[x + 1, y - 2];
                        p4 = _binaryImg[x, y - 2];
                        p5 = _binaryImg[x - 1, y - 2];
                        if (!p3 && !p4 && p5 && !x1 && !x2 && x3 && x4 && !x5 && !x6 && !x7 && !x8)
                            goto End;

                        if (p3 && !p4 && !p5 && !x1 && x2 && x3 && !x4 && !x5 && !x6 && !x7 && !x8)
                            goto End;

                        // Check if pixel is the extremity of a diagonal line
                        p1 = _binaryImg[x + 2, y];
                        p2 = _binaryImg[x + 2, y - 1];
                        p12 = _binaryImg[x + 2, y + 1];
                        if (!p1 && !p2 && p12 && x1 && !x2 && !x3 && !x4 && !x5 && !x6 && !x7 && x8)
                            goto End;

                        p6 = _binaryImg[x - 2, y - 1];
                        p7 = _binaryImg[x - 2, y];
                        p8 = _binaryImg[x - 2, y + 1];
                        if (!x1 && !x2 && !x3 && !x4 && x5 && x6 && !x7 && !x8 && !p6 && !p7 && p8)
                            goto End;

                        if (!x1 && !x2 && !x3 && x4 && x5 && !x6 && !x7 && !x8 && p6 && !p7 && !p8)
                            goto End;

                        // Apply thinning rules

                        // Rule 1
                        if (!x1 && !x2 && x4 && x5 && x6 && x7)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 2
                        if (!x1 && x3 && x4 && x5 && x6 && !x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 3
                        if (x1 && x2 && x3 && x4 && !x6 && !x7)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 4
                        if (x2 && x3 && x4 && x5 && !x7 && !x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 5
                        if (!x1 && !x2 && x4 && x5 && !x7 && !x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 6
                        if (!x1 && x3 && x4 && !x6 && !x7 && !x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 7
                        if (!x1 && x2 && x3 && x4 && x5 && x6 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 8
                        if (x1 && x2 && x3 && x4 && x5 && x6 && !x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 9
                        if (!x1 && !x2 && !x3 && x5 && x6 && !x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 10
                        if (!x1 && !x2 && !x3 && !x4 && x6 && x7)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 11
                        if (x2 && x3 && !x5 && !x6 && !x7 && !x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 12
                        if (x1 && x2 && !x4 && !x5 && !x6 && !x7)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 13
                        if (!x2 && !x3 && !x4 && !x5 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 14
                        if (x1 && !x3 && !x4 && !x5 && !x6 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 15
                        if (x1 && x2 && x3 && x4 && !x5 && x6 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 16
                        if (x1 && x2 && !x3 && x4 && x5 && x6 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 17
                        if (x1 && x2 && !x4 && !x5 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 18
                        if (x1 && x2 && x3 && !x5 && !x6 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 19
                        if (x1 && !x3 && !x4 && x6 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                        // Rule 20
                        if (!x2 && !x3 && x5 && x6 && x7 && x8)
                        {
                            PixelToDelete.X = x;
                            PixelToDelete.Y = y;
                            Stack.Push(PixelToDelete);
                            bPixelDeleted = true;
                            goto End;
                        }

                    End:
                        continue;
                    }
                }

                // Delete marked pixels
                while (Stack.Count > 0)
                {
                    Point p = (Point)Stack.Pop();
                    int x = p.X;
                    int y = p.Y;
                    _binaryImg[x, y] = false;
                }
                uNoIterations++;
            }// end of while iteration

            // Nibble at 2-pixel wide fragments
            for (int y = 2; y < heightM2; y++)
            {
                // for each pixel
                for (int x = 2; x < widthM2; x++)
                {
                    x0 = _binaryImg[x, y];

                    if (!x0)
                        goto NibbleEnd;
                    x1 = _binaryImg[x + 1, y];
                    x2 = _binaryImg[x + 1, y - 1];
                    x3 = _binaryImg[x, y - 1];
                    x4 = _binaryImg[x - 1, y - 1];
                    x5 = _binaryImg[x - 1, y];
                    x6 = _binaryImg[x - 1, y + 1];
                    x7 = _binaryImg[x, y + 1];
                    x8 = _binaryImg[x + 1, y + 1];

                    // Check if pixel is the extremity of a diagonal line
                    p9 = _binaryImg[x - 1, y + 2];
                    p10 = _binaryImg[x, y + 2];
                    p11 = _binaryImg[x + 1, y + 2];
                    if (!x1 && !x2 && !x3 && !x4 && !x5 && x6 && x7 && !x8 && p9 && !p10 && !p11)
                        goto NibbleEnd;

                    if (!x1 && !x2 && !x3 && !x4 && !x5 && !x6 && x7 && x8 && !p9 && !p10 && p11)
                        goto NibbleEnd;

                    p3 = _binaryImg[x + 1, y - 2];
                    p4 = _binaryImg[x, y - 2];
                    p5 = _binaryImg[x - 1, y - 2];
                    if (!p3 && !p4 && p5 && !x1 && !x2 && x3 && x4 && !x5 && !x6 && !x7 && !x8)
                        goto NibbleEnd;

                    if (p3 && !p4 && !p5 && !x1 && x2 && x3 && !x4 && !x5 && !x6 && !x7 && !x8)
                        goto NibbleEnd;

                    // Check if pixel is the extremity of a diagonal line
                    p1 = _binaryImg[x + 2, y];
                    p2 = _binaryImg[x + 2, y - 1];
                    p12 = _binaryImg[x + 2, y + 1];
                    if (!p1 && !p2 && p12 && x1 && !x2 && !x3 && !x4 && !x5 && !x6 && !x7 && x8)
                        goto NibbleEnd;

                    p6 = _binaryImg[x - 2, y - 1];
                    p7 = _binaryImg[x - 2, y];
                    p8 = _binaryImg[x - 2, y + 1];
                    if (!x1 && !x2 && !x3 && !x4 && x5 && x6 && !x7 && !x8 && !p6 && !p7 && p8)
                        goto NibbleEnd;

                    if (!x1 && !x2 && !x3 && x4 && x5 && !x6 && !x7 && !x8 && p6 && !p7 && !p8)
                        goto NibbleEnd;

                    // Identify vertical or horizontal 2-pixel wide line
                    if ((!x5 && x1 && !p1) || (!p7 && x5 && !x1) || (!x3 && x7 && !p10) || (!p4 && x3 && !x7))
                    {
                        // Build adjacency sub-graph for current pixel
                        bool[,] abAdjacencyMatrix = new bool[9, 9];
                        for (uint i = 1; i < 9; i++)
                            for (uint j = 1; j < 9; j++)
                                abAdjacencyMatrix[i, j] = false;

                        if (x1)
                        {
                            if (x2)
                                abAdjacencyMatrix[1, 2] = true;

                            if (x3)
                                abAdjacencyMatrix[1, 3] = true;

                            if (x7)
                                abAdjacencyMatrix[1, 7] = true;

                            if (x8)
                                abAdjacencyMatrix[1, 8] = true;
                        }

                        if (x2)
                        {
                            if (x1)
                                abAdjacencyMatrix[2, 1] = true;

                            if (x3)
                                abAdjacencyMatrix[2, 3] = true;
                        }

                        if (x3)
                        {
                            if (x1)
                                abAdjacencyMatrix[3, 1] = true;

                            if (x2)
                                abAdjacencyMatrix[3, 2] = true;

                            if (x4)
                                abAdjacencyMatrix[3, 4] = true;

                            if (x5)
                                abAdjacencyMatrix[3, 5] = true;
                        }

                        if (x4)
                        {
                            if (x3)
                                abAdjacencyMatrix[4, 3] = true;

                            if (x5)
                                abAdjacencyMatrix[4, 5] = true;
                        }

                        if (x5)
                        {
                            if (x3)
                                abAdjacencyMatrix[5, 3] = true;

                            if (x4)
                                abAdjacencyMatrix[5, 4] = true;

                            if (x6)
                                abAdjacencyMatrix[5, 6] = true;

                            if (x7)
                                abAdjacencyMatrix[5, 7] = true;
                        }

                        if (x6)
                        {
                            if (x5)
                                abAdjacencyMatrix[6, 5] = true;

                            if (x7)
                                abAdjacencyMatrix[6, 7] = true;
                        }

                        if (x7)
                        {
                            if (x1)
                                abAdjacencyMatrix[7, 1] = true;

                            if (x5)
                                abAdjacencyMatrix[7, 5] = true;

                            if (x6)
                                abAdjacencyMatrix[7, 6] = true;

                            if (x8)
                                abAdjacencyMatrix[7, 8] = true;
                        }

                        if (x8)
                        {
                            if (x1)
                                abAdjacencyMatrix[8, 1] = true;

                            if (x7)
                                abAdjacencyMatrix[8, 7] = true;
                        }

                        bool[] abPixel = { false, x1, x2, x3, x4, x5, x6, x7, x8 };
                        for (uint i = 1; i < 9; i++)
                        {
                            bool bAtLeastOneArc = false;
                            for (uint j = 1; j < 9; j++)
                                if (abPixel[i] && abPixel[j] && abAdjacencyMatrix[i, j])
                                {
                                    bAtLeastOneArc = true;
                                    break;
                                }
                            if (abPixel[i] && !bAtLeastOneArc)
                                goto NibbleEnd;
                        }

                        _binaryImg[x, y] = false;
                    }
                NibbleEnd: continue;
                }
            } // end of Nibble for-loop

            logText = "Iteration: " + uNoIterations.ToString();
            System.Diagnostics.Debug.WriteLine("Iteration: " + uNoIterations);

            // Set edge pixels to false
            for (int y = 0; y < 3; y++)
                for (int x = 0; x < width; x++)
                    _binaryImg[x, y] = false;
            for (int y = heightM2; y < height; y++)
                for (int x = 0; x < width; x++)
                    _binaryImg[x, y] = false;
            for (int y = 3; y < heightM2; y++)
                for (int x = 0; x < 3; x++)
                    _binaryImg[x, y] = false;
            for (int y = 3; y < heightM2; y++)
                for (int x = widthM2; x < width; x++)
                    _binaryImg[x, y] = false;
            #endregion

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    if (_binaryImg[x, y])
                    {
                        dstMatrix[y, x] = 0.0;
                    }
                    else
                    {
                        dstMatrix[y, x] = 255.0;
                    }
                }
            }

            logText = logText + " " + thresholdLog + " " + binarizeLog;
            return dstMatrix;
        } // ApplyDoubleGraylevelImageFilter()

        protected override PZMath_matrix[] ApplyDoubleColourImageFilter(PZMath_matrix[] srcMatrixArray, out string logText)
        {
            // TODO: add this function
            
            throw new Exception("The method or operation is not implemented.");
        } // ApplyDoubleColourImageFilter()
        #endregion

        //#region Split thinning result
        ///// <summary>
        ///// split thinning result into line segment collection
        ///// </summary>
        ///// <param name="srcImg">thinning result image, in black/white</param>
        ///// <returns># of line segments</returns>
        //public int SplitThinningResult(Bitmap srcImage)
        //{
        //    return SplitThinningResult(srcImage, out _log);
        //}
        
        //private int SplitThinningResult(Bitmap srcImage, out string logText)
        //{
        //    int width = srcImage.Width;
        //    int height = srcImage.Height;
            
        //    // generate bool image, white - background - false; black - line - true
        //    BitmapData srcImageData = srcImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
        //    int srcOffset = srcImageData.Stride - width;
        //    bool[,] temp;
        //    if (_binaryImg != null)
        //        temp = _binaryImg;
        //    _binaryImg = new bool[width, height];
        //    bool[,] originalBinaryImg = new bool[width, height];
        //    bool[,] recordBinaryImg = new bool[width, height];
        //    unsafe
        //    {
        //        byte* src = (byte*)srcImageData.Scan0.ToPointer();
        //        for (int y = 0; y < height; y++)
        //        {
        //            for (int x = 0; x < width; x++, src++)
        //            {
        //                int intensity = Convert.ToInt16(*src);
        //                if (intensity > 125)    // white
        //                    _binaryImg[x, y] = false;
        //                else
        //                    _binaryImg[x, y] = true;
        //                originalBinaryImg[x, y] = _binaryImg[x, y];
        //                recordBinaryImg[x, y] = _binaryImg[x, y];
        //            }
        //            src += srcOffset;
        //        }
        //    }
        //    srcImage.UnlockBits(srcImageData);

        //    // two stact
        //    Stack<PZPointPair> currentSearchingPointPairStack = new Stack<PZPointPair>();
        //    Stack<LineSegment> currentLineSegmentStack = new Stack<LineSegment>();
        //    for (int y = 0; y < height; y++)
        //    {
        //        for (int x = 0; x < width; x++)
        //        {
        //            if (recordBinaryImg[x, y])
        //            // fine a pixel belonging to a line segment
        //            {
        //                PZPoint currentPoint = new PZPoint(x, y);
        //                List<PZPoint> neightbour = Search8Neighbours(recordBinaryImg, currentPoint);
        //                if (neightbour.Count == 2)
        //                    // search to one end point
        //                    currentPoint = SearchEndPoint(recordBinaryImg, currentPoint);

        //                // start search from end point (assume!!!)
        //                neightbour = Search8Neighbours(recordBinaryImg, currentPoint);
        //                if (neightbour.Count > 0)
        //                {
        //                    // lastPoint:currentPoint
        //                    PZPointPair searchPointPair = new PZPointPair(currentPoint, neightbour[0]);
        //                    currentSearchingPointPairStack.Push(searchPointPair);

        //                    LineSegment currentLineSegment = new LineSegment();
        //                    currentLineSegment.AddEndPoint(currentPoint);
        //                    currentLineSegmentStack.Push(currentLineSegment);
        //                    recordBinaryImg[(int)currentPoint.x, (int)currentPoint.y] = false;

        //                    List<PZPoint> connectPointList = SearchLineSegment(originalBinaryImg, recordBinaryImg,
        //                        currentSearchingPointPairStack, currentLineSegmentStack, 
        //                        _lineSegmentList);
        //                    _connectPointList.AddRange(connectPointList);
        //                }
                                                
        //                //// draw inter-media result
        //                //PrepareThinningResultImage();
        //                //_thinningResultImage.Save("inter result.bmp", ImageFormat.Bmp);
        //                //// check recordBinaryImg
        //                //Bitmap recordImage = BoolMatrixToBitmap(recordBinaryImg);
        //                //recordImage.Save("record image.bmp", ImageFormat.Bmp);
        //            }
        //        }
        //    }
        //    logText  = "# of line segments: " + _lineSegmentList.Count;
        //    System.Console.WriteLine(logText);

        //    PrepareThinningResultImage();

        //    return _lineSegmentList.Count;
        //}

        ///// <summary>
        ///// prepare thinning result image
        ///// </summary>
        //private void PrepareThinningResultImage()
        //{
        //    int width = _binaryImg.GetLength(0);
        //    int height = _binaryImg.GetLength(1);
        //    _thinningResultImage = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            
        //    #region set background white
        //    // lock destination bitmap data
        //    BitmapData dstData = _thinningResultImage.LockBits(
        //        new Rectangle(0, 0, width, height),
        //        ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

        //    int stride = dstData.Stride;
        //    int offset = stride - width * 3;
        //    unsafe
        //    {
        //        byte* dst = (byte*)dstData.Scan0.ToPointer();
        //        for (int y = 0; y < height; y++)
        //        {
        //            for (int x = 0; x < width; x++, dst += 3)
        //            {
        //                dst[0] = 255;
        //                dst[1] = 255;
        //                dst[2] = 255;
        //            }
        //            dst += offset;
        //        }
        //    }
        //    _thinningResultImage.UnlockBits(dstData);
        //    #endregion

        //    // draw line segment on dstImg
        //    for (int i = 0; i < _lineSegmentList.Count; i++)
        //    {
        //        LineSegment lineSegment = _lineSegmentList[i];
        //        for (int j = 0; j < lineSegment.Ls; j++)
        //        {
        //            PZPoint p = lineSegment.PointList[j];
        //            int x = (int)p.x;
        //            int y = (int)p.y;
        //            _thinningResultImage.SetPixel(x, y, Color.Blue);
        //        }
        //    }

        //    // draw connect point
        //    foreach (PZPoint connectPoint in _connectPointList)
        //    {
        //        int x = (int)connectPoint.x;
        //        int y = (int)connectPoint.y;
        //        _thinningResultImage.SetPixel(x, y, Color.Red);
        //    }
        //}

        ///// <summary>
        ///// search line segment
        ///// </summary>
        ///// <param name="binaryImg"></param>
        ///// <param name="currentSearchingPointStack"></param>
        ///// <param name="lineSegmentList"></param>
        //private List<PZPoint> SearchLineSegment(bool[,] originalBinaryImg, bool[,] recordBinaryImg,
        //    Stack<PZPointPair> currentSearchingPointPairStack, Stack<LineSegment> currentLineSegmentStack, 
        //    List<LineSegment> lineSegmentList)
        //{
        //    List<PZPointPair> branchPointPairList = new List<PZPointPair>();
        //    List<PZPoint> connectPointList = new List<PZPoint>();

        //    while (currentSearchingPointPairStack.Count > 0)
        //    {
        //        // pop last point : current point  from stack
        //        PZPointPair currentPointPair = currentSearchingPointPairStack.Pop();
        //        PZPoint currentPoint = new PZPoint(currentPointPair.P2);
        //        PZPoint lastPoint = new PZPoint(currentPointPair.P1);

        //        //pop current line segment from stack
        //        LineSegment currentLineSegment = currentLineSegmentStack.Pop();
                
        //        // check next point(s)
        //        List<PZPoint> nextPointList = IsConnectPoint(originalBinaryImg, lastPoint, currentPoint);
        //        if (nextPointList.Count == 0)
        //        // current point is the end of the current line segment
        //        // seal the current line segment at the current point
        //        {
        //            currentLineSegment.AddEndPoint(currentPoint);
        //            recordBinaryImg[(int)currentPoint.x, (int)currentPoint.y] = false;
        //            //currentLineSegment.Index = lineSegmentList.Count;
        //            lineSegmentList.Add(currentLineSegment);
        //        }
        //        else if (nextPointList.Count == 1)
        //        // current point is not a connect point
        //        // extend the current line segment to its next point
        //        {
        //            PZPoint connectPoint = new PZPoint();
        //            if (IsInBranchePointPairList(branchPointPairList, nextPointList[0], ref connectPoint))
        //            // next point belongs to branch point
        //            // seal the current line segment at this connectPoint:branchPoint pair
        //            {
        //                // add current point to the current line segment
        //                currentLineSegment.AddPoint(currentPoint);
        //                recordBinaryImg[(int)currentPoint.x, (int)currentPoint.y] = false;
        //                // add next point to the current line segment
        //                currentLineSegment.AddPoint(nextPointList[0]);
        //                recordBinaryImg[(int)nextPointList[0].x, (int)nextPointList[0].y] = false;
        //                // add the corresponding connect point to the current line segment
        //                currentLineSegment.AddEndPoint(connectPoint);
        //                recordBinaryImg[(int)connectPoint.x, (int)connectPoint.y] = false;
        //                // save the current line segment to the line segment list
        //                //currentLineSegment.Index = lineSegmentList.Count;
        //                lineSegmentList.Add(currentLineSegment);
        //            }
        //            else
        //            // extend the current line segment to the next point
        //            {
        //                // add current point to the current line segment
        //                currentLineSegment.AddPoint(currentPoint);
        //                recordBinaryImg[(int)currentPoint.x, (int)currentPoint.y] = false;
        //                // push new lastPoint:currentPoint pair into searching point pair stack
        //                PZPointPair pointPair = new PZPointPair(currentPoint, nextPointList[0]);
        //                currentSearchingPointPairStack.Push(pointPair);
        //                // push the current line segment into stack
        //                currentLineSegmentStack.Push(currentLineSegment);
        //            }
        //        }
        //        else
        //        // the current point is a connect point
        //        {
        //            // save the current point in conectPointList
        //            connectPointList.Add(currentPoint);

        //            // seal the current line segment at the current point
        //            currentLineSegment.AddEndPoint(currentPoint);
        //            recordBinaryImg[(int)currentPoint.x, (int)currentPoint.y] = false;
        //            //currentLineSegment.Index = lineSegmentList.Count;
        //            lineSegmentList.Add(currentLineSegment);

        //            // check current point's neighbours
        //            List<PZPointPair> checkPointPairList = new List<PZPointPair>();
        //            foreach (PZPoint neighbour in nextPointList)
        //            {
        //                PZPointPair pointPair = new PZPointPair(currentPoint, neighbour);
        //                checkPointPairList.Add(pointPair);
        //            }
        //            while (checkPointPairList.Count > 0)
        //            {
        //                foreach (PZPointPair pointPair in checkPointPairList)
        //                {
        //                    PZPoint connectPoint = new PZPoint(pointPair.P1);
        //                    PZPoint branchPoint = new PZPoint(pointPair.P2);
        //                    PZPoint newConnectPoint = new PZPoint();

        //                    if (IsInBranchePointPairList(branchPointPairList, branchPoint, ref newConnectPoint))
        //                    // branch point already belong to branch point pair list
        //                    {
        //                        // generate a new line segment
        //                        // start from the connect point (current point)
        //                        // end with branchPoint:connectPoint pair, saved in branchPointPairList
        //                        LineSegment lineSegment = new LineSegment();
        //                        lineSegment.AddEndPoint(connectPoint);
        //                        recordBinaryImg[(int)connectPoint.x, (int)connectPoint.y] = false;
        //                        lineSegment.AddPoint(branchPoint);
        //                        recordBinaryImg[(int)branchPoint.x, (int)branchPoint.y] = false;
        //                        lineSegment.AddEndPoint(newConnectPoint);
        //                        recordBinaryImg[(int)newConnectPoint.x, (int)newConnectPoint.y] = false;
        //                        //lineSegment.Index = lineSegmentList.Count;
        //                        lineSegmentList.Add(lineSegment);
        //                        // remove the current point pair from check list
        //                        checkPointPairList.Remove(pointPair);
        //                        break;
        //                    }
        //                    else
        //                    {
        //                        List<PZPoint> potentialNeighbour = IsConnectPoint(originalBinaryImg, connectPoint, branchPoint);
        //                        if ((potentialNeighbour.Count > 1) || (IsInConnectPointList(connectPointList, branchPoint)))
        //                        // the branch point is a connect point
        //                        {
        //                            // generate a new line segment
        //                            // start from the connect point (current point)
        //                            // end with branchPoint(also a connect point)
        //                            LineSegment lineSegment = new LineSegment();
        //                            lineSegment.AddEndPoint(connectPoint);
        //                            recordBinaryImg[(int)connectPoint.x, (int)connectPoint.y] = false;
        //                            lineSegment.AddEndPoint(branchPoint);
        //                            recordBinaryImg[(int)branchPoint.x, (int)branchPoint.y] = false;
        //                            //lineSegment.Index = lineSegmentList.Count;
        //                            lineSegmentList.Add(lineSegment);
        //                            // add the branch point into connect point list
        //                            if (!IsInConnectPointList(connectPointList, branchPoint))
        //                                connectPointList.Add(branchPoint);
        //                            // remove the current point pair from check list
        //                            checkPointPairList.Remove(pointPair);
        //                            // add new connectPoint:branchPoint pair into check list
        //                            foreach (PZPoint temp in potentialNeighbour)
        //                            {
        //                                PZPointPair tempPointPair = new PZPointPair(branchPoint, temp);
        //                                checkPointPairList.Add(tempPointPair);
        //                            }
        //                            break;
        //                        }
        //                        else
        //                        // the branch point is not a connect point
        //                        // and does not belong to branch point pair list,
        //                        // hence, it is a new branch point.
        //                        {
        //                            // add to branch point pair list
        //                            branchPointPairList.Add(pointPair);
        //                            // remove from check list
        //                            checkPointPairList.Remove(pointPair);
        //                            break;
        //                        }
        //                    }
        //                }
        //            }
        //        }

        //        // push one of branch point into search point stack if there is
        //        if (currentSearchingPointPairStack.Count == 0 && branchPointPairList.Count > 0)
        //        {
        //            PZPointPair nextPointPair = branchPointPairList[0];
        //            PZPoint newLastPoint = nextPointPair.P1;
        //            LineSegment newLineSegment = new LineSegment();
        //            newLineSegment.AddEndPoint(newLastPoint);
        //            currentSearchingPointPairStack.Push(nextPointPair);
        //            currentLineSegmentStack.Push(newLineSegment);
        //            branchPointPairList.RemoveAt(0);
        //        }
        //    }
        //    return connectPointList;
        //}

        ///// <summary>
        ///// is current point the connect point?
        ///// </summary>
        ///// <param name="originalBinaryImg"></param>
        ///// <param name="lastPoint"></param>
        ///// <param name="currentPoint"></param>
        ///// <returns></returns>
        //private List<PZPoint> IsConnectPoint(bool[,] originalBinaryImg, PZPoint lastPoint, PZPoint currentPoint)
        //{
        //    List<PZPoint> nextPoints = new List<PZPoint>();
        //    // search 4N
        //    List<PZPoint> fourNeighbour = Search4Neighbours(originalBinaryImg, currentPoint);
        //    // search diagonal 4N
        //    List<PZPoint> diagonalFourNeighbour = SearchDiagonal4Neighbours(originalBinaryImg, currentPoint);
        //    if (fourNeighbour.Count + diagonalFourNeighbour.Count == 1)
        //        // no next point
        //        return nextPoints;
        //    else if (fourNeighbour.Count + diagonalFourNeighbour.Count == 2)
        //    // only one next point (one of them is lastPoint)
        //    {
        //        /*
        //        for (int i = 0; i < fourNeighbour.Count; i ++)
        //            if (fourNeighbour[i].Equals(lastPoint))
        //            {
        //                fourNeighbour.RemoveAt(i);
        //                break;
        //            }

        //        for (int i = 0; i < diagonalFourNeighbour.Count; i++)
        //            if (diagonalFourNeighbour[i].Equals(lastPoint))
        //            {
        //                diagonalFourNeighbour.RemoveAt(i);
        //                break;
        //            }
        //        */
        //        fourNeighbour.Remove(lastPoint);
        //        diagonalFourNeighbour.Remove(lastPoint);
        //        if (fourNeighbour.Count > 0)
        //            nextPoints.Add(fourNeighbour[0]);
        //        else
        //            nextPoints.Add(diagonalFourNeighbour[0]);

        //        return nextPoints;
        //    }
        //    else
        //    // more than one nextPoint
        //    {
        //        // remove lastPoint from fourNeighbour and diagonalFourNeighbour
        //        fourNeighbour.Remove(lastPoint);
        //        diagonalFourNeighbour.Remove(lastPoint);

        //        // search in diagonal four neighbours, find those not having 4N relationship with 4N(s)
        //        List<PZPoint> effectNeighbour = new List<PZPoint>(diagonalFourNeighbour);
        //        foreach (PZPoint fourN in fourNeighbour)
        //        {
        //            if (effectNeighbour.Count > 0)
        //                effectNeighbour = SearchEffectNeighbour(effectNeighbour, fourN);
        //            else
        //                break;
        //        }
        //        // search in diagonal four neighbours, find those not having 4N relationship with lastPoint
        //        if (effectNeighbour.Count > 0)
        //            effectNeighbour = SearchEffectNeighbour(effectNeighbour, lastPoint);

        //        // return next points
        //        foreach (PZPoint temp in fourNeighbour)
        //            nextPoints.Add(temp);
        //        foreach (PZPoint temp in effectNeighbour)
        //            nextPoints.Add(temp);

        //        return nextPoints;
        //    }
        //}    

        ///// <summary>
        ///// is p in connect point list?
        ///// </summary>
        ///// <param name="connectPointList"></param>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private bool IsInConnectPointList(List<PZPoint> connectPointList, PZPoint p)
        //{
        //    bool isInConnectPointList = false;
        //    foreach (PZPoint point in connectPointList)
        //        if (point.Equals(p))
        //        {
        //            isInConnectPointList = true;
        //            break;
        //        }
        //    return isInConnectPointList;
        //}

        ///// <summary>
        ///// does p belong to branch point pair list?
        ///// </summary>
        ///// <param name="branchPointPairList"></param>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private bool IsInBranchePointPairList(List<PZPointPair> branchPointPairList, PZPoint p, ref PZPoint connectPoint)
        //{
        //    bool isInBranchePointPairList = false;
        //    foreach (PZPointPair pointPair in branchPointPairList)
        //    {
        //        PZPoint branchPoint = new PZPoint(pointPair.P2);
        //        if (branchPoint.Equals(p))
        //        {
        //            isInBranchePointPairList = true;
        //            // return connect point 
        //            connectPoint = new PZPoint(pointPair.P1);
        //            // remove this connectPoint:branchPoint pair from branchPointPairList
        //            branchPointPairList.Remove(pointPair);
        //            break;
        //        }
        //    }

        //    return isInBranchePointPairList;
        //}

        ///// <summary>
        ///// search in diagonal four neighbour, choose those not having 4N relationship with point p
        ///// </summary>
        ///// <param name="diagonalFourNeighbour"></param>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private List<PZPoint> SearchEffectNeighbour(List<PZPoint> diagonalFourNeighbour, PZPoint p)
        //{
        //    List<PZPoint> effectNeighbour = new List<PZPoint>();
        //    foreach (PZPoint temp in diagonalFourNeighbour)
        //    {
        //        if ((temp.x == p.x && temp.y == p.y + 1)
        //            || (temp.x == p.x && temp.y == p.y - 1)
        //            || (temp.x == p.x + 1 && temp.y == p.y)
        //            || (temp.x == p.x - 1 && temp.y == p.y))
        //            continue;
        //        else
        //            effectNeighbour.Add(temp);
        //    }
        //    return effectNeighbour;
        //}

        ///// <summary>
        ///// search end point
        ///// </summary>
        ///// <param name="binarImg"></param>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private PZPoint SearchEndPoint(bool[,] binarImg, PZPoint p)
        //{
        //    List<PZPoint> neighbour = Search8Neighbours(binarImg, p);
        //    if (neighbour.Count == 1)
        //        return p;
        //    else
        //        return SearchEndPoint(binarImg, neighbour[0]);
        //}

        ///// <summary>
        ///// search 8 neighbours
        ///// </summary>
        ///// <param name="row"></param>
        ///// <param name="col"></param>
        ///// <returns></returns>
        //private List<PZPoint> Search8Neighbours(bool[,] binaryImg, PZPoint p)
        //{
        //    int width = binaryImg.GetLength(0);
        //    int height = binaryImg.GetLength(1);
        //    List<PZPoint> neighbours = new List<PZPoint>();
        //    int x = (int) p.x;
        //    int y = (int) p.y;
        //    if (x - 1 >= 0 && y - 1 >= 0)
        //        if (binaryImg[x - 1, y - 1])
        //            neighbours.Add(new PZPoint(x - 1, y - 1));
        //    if (x - 1 >= 0)
        //        if (binaryImg[x - 1, y])
        //            neighbours.Add(new PZPoint(x - 1, y));
        //    if (x - 1 >= 0 && y + 1 < height)
        //        if (binaryImg[x - 1, y + 1])
        //            neighbours.Add(new PZPoint(x - 1, y + 1));
        //    if (y - 1 >= 0)
        //        if (binaryImg[x, y - 1])
        //            neighbours.Add(new PZPoint(x, y - 1));
        //    if (y + 1 < height)
        //        if (binaryImg[x, y + 1])
        //            neighbours.Add(new PZPoint(x, y + 1));
        //    if (x + 1 < width && y - 1 >= 0)
        //        if (binaryImg[x + 1, y - 1])
        //            neighbours.Add(new PZPoint(x + 1, y - 1));
        //    if (x + 1 < width)
        //        if (binaryImg[x + 1, y])
        //            neighbours.Add(new PZPoint(x + 1, y));
        //    if (x + 1 < width && y + 1 < height)
        //        if (binaryImg[x + 1, y + 1])
        //            neighbours.Add(new PZPoint(x + 1, y + 1));

        //    return neighbours;  
        //}
        
        ///// <summary>
        ///// search 4 neighbours
        ///// </summary>
        ///// <param name="binaryImg"></param>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private List<PZPoint> Search4Neighbours(bool[,] binaryImg, PZPoint p)
        //{
        //    int width = binaryImg.GetLength(0);
        //    int height = binaryImg.GetLength(1);
        //    List<PZPoint> neighbours = new List<PZPoint>();
        //    int x = (int)p.x;
        //    int y = (int)p.y;
        //    if (x - 1 >= 0)
        //        if (binaryImg[x - 1, y])
        //            neighbours.Add(new PZPoint(x - 1, y));
        //    if (y - 1 >= 0)
        //        if (binaryImg[x, y - 1])
        //            neighbours.Add(new PZPoint(x, y - 1));
        //    if (y + 1 < height)
        //        if (binaryImg[x, y + 1])
        //            neighbours.Add(new PZPoint(x, y + 1));
        //    if (x + 1 < width)
        //        if (binaryImg[x + 1, y])
        //            neighbours.Add(new PZPoint(x + 1, y));
        //    return neighbours;
        //}

        ///// <summary>
        ///// search diagonal 4 neighbour
        ///// </summary>
        ///// <param name="binaryImg"></param>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private List<PZPoint> SearchDiagonal4Neighbours(bool[,] binaryImg, PZPoint p)
        //{
        //    int width = binaryImg.GetLength(0);
        //    int height = binaryImg.GetLength(1);
        //    List<PZPoint> neighbours = new List<PZPoint>();
        //    int x = (int)p.x;
        //    int y = (int)p.y;
        //    if (x - 1 >= 0 && y - 1 >= 0)
        //        if (binaryImg[x - 1, y - 1])
        //            neighbours.Add(new PZPoint(x - 1, y - 1));
        //    if (x - 1 >= 0 && y + 1 < height)
        //        if (binaryImg[x - 1, y + 1])
        //            neighbours.Add(new PZPoint(x - 1, y + 1));
        //    if (x + 1 < width && y - 1 >= 0)
        //        if (binaryImg[x + 1, y - 1])
        //            neighbours.Add(new PZPoint(x + 1, y - 1));
        //    if (x + 1 < width && y + 1 < height)
        //        if (binaryImg[x + 1, y + 1])
        //            neighbours.Add(new PZPoint(x + 1, y + 1));
        //    return neighbours;
        //} // SearchDiagonal4Neighbours()

        //private Bitmap BoolMatrixToBitmap(bool[,] binaryImg)
        //{
        //    int width = binaryImg.GetLength(0);
        //    int height = binaryImg.GetLength(1);
        //    Bitmap dstImage = new Bitmap(width, height, PixelFormat.Format24bppRgb);
        //    BitmapData dstImageData = dstImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
        //    int dstOffset = dstImageData.Stride - 3 * width;
        //    unsafe
        //    {
        //        byte* dst = (byte*)dstImageData.Scan0.ToPointer();
        //        for (int y = 0; y < height; y++)
        //        {
        //            for (int x = 0; x < width; x++, dst += 3)
        //            {
        //                if (binaryImg[x, y])    // true - black
        //                {
        //                    *dst = 0;
        //                    *(dst + 1) = 0;
        //                    *(dst + 2) = 0;
        //                }
        //                else
        //                {
        //                    *dst = 255;
        //                    *(dst + 1) = 255;
        //                    *(dst + 1) = 255;
        //                }
        //            }
        //            dst += dstOffset;
        //        }
        //    }
        //    dstImage.UnlockBits(dstImageData);
        //    return dstImage;
        //} // BoolMatrixToBitmap()

        //#endregion

        #region I/O
        public void WriteFile(string fileName)
        {
            FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write);
            StreamWriter writer = new StreamWriter(fileStream);
            int width = _binaryImg.GetLength(0);
            int height = _binaryImg.GetLength(1);
            writer.WriteLine(String.Format("{0, -20}{1, -20}{2, -20}",
                        "x", "y", "intensity"));
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    if (_binaryImg[x, y])
                        writer.WriteLine(String.Format("{0,-20:0.00}{1,-20:0.00}{2,-20:0.00}",
                            x, y, "0"));
                }
            }
            writer.Flush();
            writer.Close();
            fileStream.Close();
        }

        public void WriteOverlapTecplotFile(string fileName)
        {
            FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write);
            StreamWriter writer = new StreamWriter(fileStream);
            int width = _binaryImg.GetLength(0);
            int height = _binaryImg.GetLength(1);
            double temp = 0.0;
            double black = 0.0;
            double white = 1.0;
            writer.WriteLine("TITLE     = " + fileName);
            writer.WriteLine("VARIABLES = \"x\"");
            writer.WriteLine("\"y\"");
            writer.WriteLine("\"u\"");
            writer.WriteLine("\"v\"");
            writer.WriteLine("\"magenitude\"");
            writer.WriteLine("ZONE T = \"Zone center\"");
            writer.WriteLine("I = " + width + ", J=" + height + ", ZONETYPE=Ordered");
            writer.WriteLine("DATAPACKING = POINT");
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    if (_binaryImg[x, y])
                        writer.WriteLine(String.Format("{0,-20:0.00}{1,-20:0.00}{2,-20:0.00}{3,-20:0.00}{4,-20:0.00}",
                            x, y, temp, temp, black));
                    else
                        writer.WriteLine(String.Format("{0,-20:0.00}{1,-20:0.00}{2,-20:0.00}{3,-20:0.00}{4,-20:0.00}",
                            x, y, temp, temp, white));
                }
            }
            writer.Flush();
            writer.Close();
            fileStream.Close();
        }
        #endregion
    }
}
